package com.ccteam.shared.data

import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import com.ccteam.model.Resource
import com.ccteam.network.ApiError
import com.ccteam.network.ApiResult
import kotlinx.coroutines.flow.*

/**
 * @author Xiaoc
 * @since 2021/1/31
 *
 * 基本的Resource决策操作，它与 [Resource] 状态紧密相连
 *
 * 提供一个 [asFlow] 方法，将打包为一个Flow流，并进行以下操作
 * 1.先提供一个数据为空的等待状态（该步骤已被具体业务具体代替）
 * 2.在等待状态期间，进行数据库或内存数据的加载
 * 3.进行决策，是否要重新向网络进行请求
 *   否：则直接将数据库内容进行填入
 *   是：则进行下一步
 * 4.提供一个数据为数据库或内存检索后的等待状态
 * 5.同时进行网络检索
 * 6.将网络检索相关的数据进行回填至数据库或内存
 * 7.提供从数据库或内存中的最新数据的成功状态，如果请求网络失败则提供失败状态
 *
 * 注：该类基于单一数据源 原理，因为多数据源可能会导致的数据不正确，该类的数据源统一设定为：内存或数据库 数据源
 *
 * 相关逻辑详情可见：
 * https://developer.android.google.cn/jetpack/guide#truth
 *
 * @param ResultType 需要加载的数据的类型（因为有时服务端提供的类型与实际需要的可能需要一些转换）
 * @param RequestType 请求网络等内容时请求的类型
 **/
abstract class BaseBoundResource<ResultType,RequestType> {

    fun asFlow() = flow {

        // 先进行一次内存or数据库的加载
        val source = loadFromDbOrMemory().first()
        // 判断是否需要重新分发数据
        if(shouldFetch(source)){
            // 提交一次Loading状态的新数据
            emit(Resource.Loading(source))
            // 进行网络请求
            when(val response = fetchFromNetwork()){
                is ApiResult.Success ->{
                    // 将新内容重新放入数据库 or 内存中
                    saveNetworkResult(processResponse(response))
                    emitAll(loadFromDbOrMemory().map { Resource.Success(it) })
                }
                is ApiResult.Empty ->{
                    emitAll(loadFromDbOrMemory().map { Resource.Success(it) })
                }
                is ApiResult.Failure ->{
                    onFetchFailed()
                    emitAll(loadFromDbOrMemory().map { Resource.Error(ApiError.serverErrorCode,response.errorMsg,it) })
                }
            }
        } else {
            // 由于可能存在 zip 并发执行请求，所以如果直接走本地内存数据库要多发送一次数据
            // 这样 zip 才能正确合并另一个来自网络请求操作的数据
            // https://blog.csdn.net/mwthe/article/details/82780193
            emitAll(loadFromDbOrMemory().map { Resource.Success(it) })
            // 不需要重新请求则直接从数据库or内存中获得内容
            emitAll(loadFromDbOrMemory().map { Resource.Success(it) })
        }
    }

    /**
     * 当出现请求失败时需要做的处理（默认不做处理）
     */
    protected open fun onFetchFailed(){}

    /**
     * 从数据库或者内存中加载数据
     * @return Flow<ResultType>
     */
    @MainThread
    protected abstract fun loadFromDbOrMemory(): Flow<ResultType>

    /**
     * 处理返回响应内容
     */
    @WorkerThread
    protected open fun processResponse(response: ApiResult.Success<RequestType>) = response.data

    /**
     * 存储来自网络的新数据
     * 该方法为挂起方法，需在协程作用域中执行
     */
    @WorkerThread
    protected abstract suspend fun saveNetworkResult(item: RequestType)

    /**
     * 判断是否应该重新分发数据
     */
    @MainThread
    protected abstract fun shouldFetch(data: ResultType?): Boolean

    /**
     * 从网络上获取数据
     * @return ApiResult<RequestType> 具体Api返回的内容
     */
    protected abstract suspend fun fetchFromNetwork(): ApiResult<RequestType>
}